﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Net;
using System.IO;
using HtmlAgilityPack;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Win32;

namespace BandwidthMonitor
{
    public class Program : ApplicationContext
    {
        public enum UsageStatus
        {
            unknown = 0,
            green = 1,
            amber = 2,
            red = 3
        }

        private System.ComponentModel.IContainer components;					// a list of components to dispose when the context is disposed
        private System.Windows.Forms.NotifyIcon mainNotifyIcon;				    // the icon that sits in the system tray

        private System.Windows.Forms.ContextMenu mainNotifyIconContextMenu;	    // the context menu for the notify icon
        private System.Windows.Forms.MenuItem showContextMenuItem;			    // open menu command for context menu 	
        private System.Windows.Forms.MenuItem optionsContextMenuItem;			// options menu command for context menu
        private System.Windows.Forms.MenuItem sep3;
        private System.Windows.Forms.MenuItem exitContextMenuItem;			    // exit menu command for context menu

        private bool m_ProcessTimer = true;
        private System.Windows.Forms.Timer eventTimer;
        private System.Windows.Forms.Form mainForm;						        // the current form we're displaying

        /// <summary>
        /// Used to cancel all pending threads when shutting down the form
        /// </summary>
        CancellationTokenSource m_tokenSource = new CancellationTokenSource();
        Task m_GetDataTask;

        public int m_broadbandTotalAllowance = 0;
        public Single m_broadbandTotalUsed = 0;
        public string m_broadbandAllowanceUntil = "";
        public string m_Telephone = "";
        public string m_LastUpdate = "";

        #region Initialisation

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // TODO:DS Application.ThreadException += new ThreadExceptionEventHandler(ErrorHelper.ApplicationThreadException);
            // TODO:DS Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            // TODO:DS AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(ErrorHelper.CurrentDomainUnhandledException);

            if (!Properties.Settings.Default.Upgraded)
            {
                Properties.Settings.Default.Upgrade();
                Properties.Settings.Default.Upgraded = true;
            }

            Program applicationContext = new Program();
            
            Thread.Sleep(1500);
            //applicationContext.GetData();

            Application.EnableVisualStyles();
            Application.Run(applicationContext);

            // TODO:DS Application.ThreadException -= new ThreadExceptionEventHandler(ErrorHelper.ApplicationThreadException);
            // TODO:DS AppDomain.CurrentDomain.UnhandledException -= new UnhandledExceptionEventHandler(ErrorHelper.CurrentDomainUnhandledException);
        }

        public Program()
        {
            SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);
            InitializeContext();
        }

        void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
        {
            if (e.Mode == PowerModes.Suspend) m_ProcessTimer = false;

            if (e.Mode == PowerModes.Resume)
            {
                m_ProcessTimer = true;
                eventTimer.Interval = 1000;
            }
        }

        /// <summary>
        /// Create the NotifyIcon UI, the ContextMenu for the NotifyIcon and an Exit menu item. 
        /// </summary>
        private void InitializeContext()
        {
            this.components = new System.ComponentModel.Container();
            this.mainNotifyIcon = new System.Windows.Forms.NotifyIcon(this.components);
            this.mainNotifyIconContextMenu = new System.Windows.Forms.ContextMenu();
            this.showContextMenuItem = new System.Windows.Forms.MenuItem();
            this.optionsContextMenuItem = new System.Windows.Forms.MenuItem();
            this.sep3 = new System.Windows.Forms.MenuItem();
            this.exitContextMenuItem = new System.Windows.Forms.MenuItem();
            this.eventTimer = new System.Windows.Forms.Timer();

            // 
            // mainNotifyIcon
            // 
            this.mainNotifyIcon.ContextMenu = this.mainNotifyIconContextMenu;
            this.mainNotifyIcon.Click += new System.EventHandler(this.MainNotifyIcon_Click);
            this.mainNotifyIcon.Icon = Properties.Resources.LEDYellow;
            this.mainNotifyIcon.Text = "BT Broadband Bandwidth Monitor";
            this.mainNotifyIcon.Visible = true;

            // 
            // mainNotifyIconContextMenu
            // 
            this.mainNotifyIconContextMenu.MenuItems.AddRange(
                new MenuItem[] 
            {
                showContextMenuItem, optionsContextMenuItem, sep3, 
                exitContextMenuItem
            });

            // 
            // showContextMenuItem
            // 
            //			this.showContextMenuItem.Index = 0;
            this.showContextMenuItem.Text = "&Show";
            this.showContextMenuItem.DefaultItem = true;
            this.showContextMenuItem.Click += new System.EventHandler(this.ShowContextMenuItem_Click);

            // 
            // optionsContextMenuItem
            // 
            //            this.optionsContextMenuItem.Index = 1;
            this.optionsContextMenuItem.Text = "&Options";
            this.optionsContextMenuItem.Click += new System.EventHandler(this.OptionsContextMenuItem_Click);

            // 
            // sep3
            // 
            this.sep3.Name = "sep3";
            this.sep3.Text = "-";
            
            // 
            // exitContextMenuItem
            // 
            //			this.exitContextMenuItem.Index = 4;
            this.exitContextMenuItem.Text = "&Exit";
            this.exitContextMenuItem.Click += new System.EventHandler(this.ExitContextMenuItem_Click);

            //
            // eventTimer
            //
            this.eventTimer.Interval = 1000;
            this.eventTimer.Enabled = true;
            this.eventTimer.Tick += new System.EventHandler(this.EventTimer_Tick);
        }

        /// <summary>
        /// When the application context is disposed, dispose things like the notify icon.
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// When the open menu item is clicked, make a call to Show the form.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ShowContextMenuItem_Click(object sender, EventArgs e)
        {
            ShowForm();
        }

        /// <summary>
        /// When the exit menu item is clicked, make a call to terminate the ApplicationContext.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExitContextMenuItem_Click(object sender, EventArgs e)
        {
            // Need to make sure any threads are killed off before quitting
            m_tokenSource.Cancel();

            AppExit();
        }

        /// <summary>
        /// Options menu click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OptionsContextMenuItem_Click(object sender, EventArgs e)
        {
            if (mainForm != null) mainForm.Close();

            frmOptions options = new frmOptions();
            options.ShowDialog();

            // Force a quick refresh
            if (options.m_Ok) eventTimer.Interval = 1000;

            options = null;
        }

        /// <summary>
        /// When the notify icon is clicked in the system tray, bring up the main form
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainNotifyIcon_Click(object sender, System.EventArgs e)
        {
            if (((MouseEventArgs)e).Button == MouseButtons.Left)
                ShowForm();
        }

        private void EventTimer_Tick(object sender, System.EventArgs e)
        {
            bool execute = true;
            if (!m_ProcessTimer) return;

            if (m_GetDataTask != null)
            {
                if (m_GetDataTask.Status == TaskStatus.Canceled || m_GetDataTask.Status== TaskStatus.Faulted)
                {
                    m_tokenSource.Cancel();
                    m_GetDataTask.Dispose();
                    m_GetDataTask = null;
                }
                else
                {
                    execute = false;
                }
            }

            if (execute) GetData();
        }

        private void MainForm_Closed(object sender, EventArgs e)
        {
            // null out the main form so we know to create a new one.
            this.mainForm = null;
        }

        #endregion

        #region Methods

        private void GetData()
        {
            TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
            m_GetDataTask = Task.Factory.StartNew(() => GetDataThread(), m_tokenSource.Token).ContinueWith(t => DataRetrieved(), m_tokenSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, ui);
        }

        private void GetDataThread()
        {
            this.mainNotifyIcon.Icon = Properties.Resources.LEDLightBlue;

            bool ok = false;

            // first, request the login form
            Uri uri = new Uri("https://www.bt.com/appsauth/genericlogin.do");
            HttpWebRequest webRequest = WebRequest.Create(uri) as HttpWebRequest;
            StreamReader responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());
            string responseData = responseReader.ReadToEnd();
            responseReader.Close();

            // Build POST data
            // TODO:DS username and password should be options
            string userName = Properties.Settings.Default.BTUsername;
            string password = Properties.Settings.Default.BTPassword;

            string postData = "cookieExpp=30" +
                "&switch=yes" +
                "&TARGET=/appsauth/login/forward.do?url=http://www.bt.com" +
                "&SMPostLoginUrl=/appsyouraccount/secure/postlogin" +
                "&smauthreason=0" +
                string.Format("&USER={0}&PASSWORD={1}", userName, password) +
                "&rememberMe=on" +
                "&x=28&y=8";

            // Have a cookie container ready to receive the forms auth cookie
            CookieContainer cookies = new CookieContainer();

            // Now post to the login form
            uri = new Uri("https://www.bt.com/siteminderagent/forms/login.fcc");
            webRequest = WebRequest.Create(uri) as HttpWebRequest;
            webRequest.Method = "POST";
            webRequest.ContentType = "application/x-www-form-urlencoded";
            webRequest.Referer = "https://www.bt.com/appsauth/genericlogin.do";
            webRequest.CookieContainer = cookies;

            // Write the form values into the request message
            StreamWriter requestWriter = new StreamWriter(webRequest.GetRequestStream());
            requestWriter.Write(postData);
            requestWriter.Close();

            // We don't need the contents of the response, just the cookie it issues
            webRequest.GetResponse().Close();

            // If login succeeds we should have 4 cookies and the 1st one should be SMSESSION
            if (cookies.GetCookies(uri).Count == 4)
            {
                if (cookies.GetCookies(uri)[0].Name == "SMSESSION")
                {
                    // Now we can send out cookie along with a request for the protected page
                    uri = new Uri("https://www.bt.com/appsyouraccount/usage/getUsage.do?decorator=consumer_bbusage&acKey=3004901");
                    webRequest = WebRequest.Create(uri) as HttpWebRequest;
                    webRequest.CookieContainer = cookies;
                    responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream());

                    // And read the response
                    responseData = responseReader.ReadToEnd();
                    responseReader.Close();

                    // Parse the responseData
                    // See the <div class="fallback"> section in SampleResultPage.htm
                    HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlAgilityPack.HtmlDocument();
                    htmlDoc.OptionFixNestedTags = true;
                    htmlDoc.LoadHtml(responseData);

                    // ParseErrors is an ArrayList containing any errors from the Load statement
                    if (htmlDoc.ParseErrors != null && htmlDoc.ParseErrors.Count() > 0)
                    {
                        // Handle any parse errors as required   
                    }
                    else
                    {
                        if (htmlDoc.DocumentNode != null)
                        {
                            // Tel No and Last update
                            HtmlNode node = htmlDoc.DocumentNode.SelectSingleNode(".//dl[dt = 'Telephone number:']");
                            if (node != null)
                            {

                                m_Telephone = node.SelectSingleNode("dd").InnerHtml;
                                m_LastUpdate = node.SelectNodes("dd")[1].InnerHtml;
                            }

                            // Usage data
                            HtmlNode data = htmlDoc.DocumentNode.SelectSingleNode(".//div[@class = 'fallback']");
                            if (node != null)
                            {
                                HtmlNodeCollection nodeList = data.SelectNodes("table/tbody/tr");

                                if (nodeList != null)
                                {
                                    foreach (HtmlNode n in nodeList)
                                    {
                                        if (n.SelectSingleNode("th").InnerText.Trim() == "Allocated Data Limit:")
                                        {
                                            string value = n.SelectSingleNode("td").InnerHtml;
                                            m_broadbandTotalAllowance = Convert.ToInt32(value.Substring(0, value.IndexOf(' ')));

                                            string subValue = n.SelectSingleNode("td/span").InnerHtml;
                                            m_broadbandAllowanceUntil = subValue.ToString();
                                        }
                                        if (n.SelectSingleNode("th").InnerText.Trim() == "Used Data:")
                                        {
                                            string value = n.SelectSingleNode("td").InnerHtml;
                                            m_broadbandTotalUsed = Convert.ToSingle(value.Substring(0, value.IndexOf(' ')));
                                        }
                                    }
                                }
                            }
                        }

                        ok = true;
                    }
                }
            }

            if (!ok) m_broadbandTotalAllowance = 0;
            UpdateStatus();
        }

        public void DataRetrieved()
        {
            m_GetDataTask = null;
            eventTimer.Interval = 14400000;
        }

        public void UpdateStatus()
        {
            // Update the tray icon
            switch (CurrentStatus())
            {
                case UsageStatus.unknown:
                    this.mainNotifyIcon.Icon = Properties.Resources.LEDYellow;
                    break;
                case UsageStatus.green:
                    this.mainNotifyIcon.Icon = Properties.Resources.LEDGreen;
                    break;
                case UsageStatus.amber:
                    this.mainNotifyIcon.Icon = Properties.Resources.LEDOrange;
                    break;
                case UsageStatus.red:
                    this.mainNotifyIcon.Icon = Properties.Resources.LEDRed;
                    break;
            }
        }

        public UsageStatus CurrentStatus()
        {
            // Update the tray icon
            if (m_broadbandTotalAllowance > 0)
            {
                if (PercentageUsed < Properties.Settings.Default.AmberThreshold) return UsageStatus.green;
                if (PercentageUsed >= Properties.Settings.Default.AmberThreshold && PercentageUsed < Properties.Settings.Default.RedThreshold) return UsageStatus.amber;
                if (PercentageUsed >= Properties.Settings.Default.RedThreshold) return UsageStatus.red;
            }
            return UsageStatus.unknown;
        }

        public void AppExit()
        {
            SystemEvents.PowerModeChanged -= new PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);

            mainNotifyIcon.Dispose();
            mainNotifyIcon = null;

            // TODO:DS Application.ThreadException -= new ThreadExceptionEventHandler(ErrorHelper.ApplicationThreadException);
            // TODO:DS AppDomain.CurrentDomain.UnhandledException -= new UnhandledExceptionEventHandler(ErrorHelper.CurrentDomainUnhandledException);

            ExitThread();
        }

        /// <summary>
        /// If we are presently showing a mainForm, clean it up.
        /// </summary>
        protected override void ExitThreadCore()
        {
            if (mainForm != null)
            {
                // before we exit, give the main form a chance to clean itself up.
                mainForm.Close();
            }

            base.ExitThreadCore();
        }

        /// <summary>
        /// This function will either create a new Form or activate the existing one, bringing the 
        /// window to front.
        /// </summary>
        private void ShowForm()
        {
            if (mainForm == null)
            {
                // create a fresh new frmMain and show it.
                mainForm = new frmMain(this);
                mainForm.TopMost = true;
                mainForm.Show();

                // hook onto the closed event so we can null out the main form...  this avoids reshowing
                // a disposed form.
                mainForm.Closed += new EventHandler(MainForm_Closed);
            }
            else
            {
                // the form is currently visible, so close it
                mainForm.Close();
            }
        }

        #endregion

        #region Properties

        public float PercentageUsed
        {
            get
            {
                return ((m_broadbandTotalUsed / m_broadbandTotalAllowance) * 100);
            }
        }

        #endregion
    }
}
